
package uk.org.cardboardbox.problemmachine;

import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu;
import com.ipaulpro.afilechooser.FileChooserActivity;
import com.ipaulpro.afilechooser.utils.FileUtils;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;

public class ProblemMachine extends SherlockActivity {

    private static final String TAG = "ProblemMachine";

    private ListView mMemoryList;

    private Context mContext;

    private Processor processor = new Processor();

    private FourBitDisplay pc;

    private FourBitDisplay a;

    private FourBitDisplay shadowa;

    private FourBitDisplay out;

    private FourBitInput in;

    private View z;

    private Button step;

    private Button run;

    private Button stop;

    private Button reset;

    private TextView clocks;

    private Handler mHandler;

    private boolean mStopStepping = false;

    private AlertDialog help;

    private AlertDialog clear;

    private static final int REQUEST_CODE_LOAD = 1234;

    private static final int REQUEST_CODE_SAVE = 4321;

    private static final String CHOOSER_TITLE = "Select a file";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        help = new AlertDialog.Builder(this)
                .setTitle("How to use this app")
                .setMessage(
                        "This application is what is known as a \"Turing tarpit\". It simulates a very limited computer.\n"
                                + //
                                "To enter instructions long click a memory location.\n" + //
                                "INPUT port bits can be toggled by clicking them." //
                ).setNeutralButton("Okey dokey!", null).create();

        clear = new AlertDialog.Builder(this)
                .setTitle("Clear memory?")
                .setMessage(
                        "Are you really sure you want to clear the memory? This cannot be undone!")
                .setNegativeButton("Abort!", null)
                .setPositiveButton("Do it!", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        processor.clear();
                        mMemoryList.invalidateViews();
                    }
                }).create();

        mContext = this;

        mMemoryList = (ListView)this.findViewById(R.id.memorylist);
        mMemoryList.setAdapter(mMemoryAdapter);

        pc = new FourBitDisplay(this.findViewById(R.id.pcindicator_four),
                this.findViewById(R.id.pcindicator_three), this.findViewById(R.id.pcindicator_two),
                this.findViewById(R.id.pcindicator_one));

        a = new FourBitDisplay(this.findViewById(R.id.aindicatorfour),
                this.findViewById(R.id.aindicatorthree), this.findViewById(R.id.aindicatortwo),
                this.findViewById(R.id.aindicatorone));

        shadowa = new FourBitDisplay(this.findViewById(R.id.shadowaindicatorfour),
                this.findViewById(R.id.shadowaindicatorthree),
                this.findViewById(R.id.shadowaindicatortwo),
                this.findViewById(R.id.shadowaindicatorone));

        out = new FourBitDisplay(this.findViewById(R.id.outindicatorfour),
                this.findViewById(R.id.outindicatorthree), this.findViewById(R.id.outindicatortwo),
                this.findViewById(R.id.outindicatorone));

        in = new FourBitInput((ImageView)this.findViewById(R.id.inindicatorfour),
                (ImageView)this.findViewById(R.id.inindicatorthree),
                (ImageView)this.findViewById(R.id.inindicatortwo),
                (ImageView)this.findViewById(R.id.inindicatorone));

        z = (View)this.findViewById(R.id.zindicator);

        clocks = (TextView)this.findViewById(R.id.clocks);

        this.refresh(false);
        step = (Button)this.findViewById(R.id.step);
        step.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                processor.step();
                ProblemMachine.this.refresh(false);
            }

        });

        run = (Button)this.findViewById(R.id.run);
        run.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mStopStepping = false;
                stepper.run();
                v.setEnabled(false);
                step.setEnabled(false);
                stop.setEnabled(true);
                reset.setEnabled(false);
            }
        });

        stop = (Button)this.findViewById(R.id.stop);
        stop.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mStopStepping = true;
                run.setEnabled(true);
                v.setEnabled(false);
                step.setEnabled(true);
                reset.setEnabled(true);
                mHandler.removeCallbacks(stepper);
            }
        });

        reset = (Button)this.findViewById(R.id.reset);
        reset.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                processor.reset();
                refresh(false);
            }
        });

        mHandler = new Handler();

        this.registerForContextMenu(mMemoryList);
    }

    private void refresh(boolean loaded) {
        pc.set(processor.getPC());

        z.setEnabled(processor.getZ());

        a.set(processor.getA());

        shadowa.set(processor.getShadowA());
        out.set(processor.getOut());

        clocks.setText(Integer.toString(processor.getClocks()));

        mMemoryList.invalidateViews();
        if (loaded)
            in.refresh();
    }

    private BaseAdapter mMemoryAdapter = new BaseAdapter() {

        @Override
        public int getCount() {
            return 0xF + 1;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            TextView view;

            if (convertView != null) {
                view = (TextView)convertView;
            } else {
                view = new TextView(mContext);
                view.setPadding(5, 5, 5, 5);
            }

            view.setText("0x" + Integer.toHexString(position) + "\t"
                    + ProblemMachine.this.opcodeToString(processor.getInstruction(position)));
            if (processor.getPC() == position) {
                view.setTextColor(Color.RED);
            } else {
                view.setTextColor(Color.WHITE);
            }
            return view;
        }

    };

    private class FourBitDisplay {

        View bitfour;

        View bitthree;

        View bittwo;

        View bitone;

        FourBitDisplay(View bitfour, View bitthree, View bittwo, View bitone) {
            this.bitfour = bitfour;
            this.bitthree = bitthree;
            this.bittwo = bittwo;
            this.bitone = bitone;
        }

        void set(int arg) {

            bitfour.setEnabled(false);
            bitthree.setEnabled(false);
            bittwo.setEnabled(false);
            bitone.setEnabled(false);

            if ((arg & 0x1) == 0x1) {
                bitone.setEnabled(true);
            }

            if ((arg & 0x2) == 0x2) {
                bittwo.setEnabled(true);
            }

            if ((arg & 0x4) == 0x4) {
                bitthree.setEnabled(true);
            }

            if ((arg & 0x8) == 0x8) {
                bitfour.setEnabled(true);
            }
        }
    }

    private class FourBitInput {

        boolean b4 = false;

        boolean b3 = false;

        boolean b2 = false;

        boolean b1 = false;

        private final ImageView bitfour;

        private final ImageView bitthree;

        private final ImageView bittwo;

        private final ImageView bitone;

        FourBitInput(ImageView bitfour, ImageView bitthree, ImageView bittwo, ImageView bitone) {
            this.bitfour = bitfour;
            this.bitthree = bitthree;
            this.bittwo = bittwo;
            this.bitone = bitone;

            this.bitfour.setOnClickListener(listener);
            this.bitthree.setOnClickListener(listener);
            this.bittwo.setOnClickListener(listener);
            this.bitone.setOnClickListener(listener);

            update();

        }

        OnClickListener listener = new OnClickListener() {
            @Override
            public void onClick(View v) {

                if (v == bitfour) {
                    b4 = !b4;
                }

                else if (v == bitthree) {
                    b3 = !b3;
                }

                else if (v == bittwo) {
                    b2 = !b2;
                }

                else if (v == bitone) {
                    b1 = !b1;
                }

                update();
            }
        };

        private int setBit(ImageView bit, boolean set, int inc) {
            if (set) {
                bit.setImageDrawable(getResources().getDrawable(R.drawable.bit_on));
                return inc;
            } else {
                bit.setImageDrawable(getResources().getDrawable(R.drawable.bit_off));
                return 0;
            }

        }

        private void update() {

            int temp = 0;

            temp += setBit(bitone, b1, 1);
            temp += setBit(bittwo, b2, 2);
            temp += setBit(bitthree, b3, 4);
            temp += setBit(bitfour, b4, 8);

            processor.setIn(temp);

        }

        private void refresh() {
            int newIn = processor.getIn();
            b1 = (newIn & 0x01) > 0;
            b2 = (newIn & 0x02) > 0;
            b3 = (newIn & 0x04) > 0;
            b4 = (newIn & 0x08) > 0;
            update();
        }
    }

    private String opcodeToString(int opcode) {

        String hex = "0x" + Integer.toHexString(opcode);
        switch (opcode) {
            case Processor.INST_SHL:
                return "SHL \t(" + hex + ")";
            case Processor.INST_SHR:
                return "SHR \t(" + hex + ")";
            case Processor.INST_IN:
                return "IN \t(" + hex + ")";
            case Processor.INST_OUT:
                return "OUT \t(" + hex + ")";

            case Processor.INST_INC:
                return "INC \t(" + hex + ")";
            case Processor.INST_DEC:
                return "DEC \t(" + hex + ")";
            case Processor.INST_ZERO:
                return "ZERO \t(" + hex + ")";
            case Processor.INST_SWAP:
                return "SWAP \t(" + hex + ")";

            case Processor.INST_JMPZ:
                return "JMPZ \t(" + hex + ")";
            case Processor.INST_JMPA:
                return "JMPA \t(" + hex + ")";
            case Processor.INST_JMPO:
                return "JMPO \t(" + hex + ")";
            case Processor.INST_JMPT:
                return "JMPT \t(" + hex + ")";

            case Processor.INST_JPZZ:
                return "JPZZ \t(" + hex + ")";
            case Processor.INST_JPZA:
                return "JPZA \t(" + hex + ")";
            case Processor.INST_JPZO:
                return "JPZO \t(" + hex + ")";
            case Processor.INST_JPZT:
                return "JPZT \t(" + hex + ")";

        }

        return null;
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {

        super.onCreateContextMenu(menu, v, menuInfo);

        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.opcode, menu);
    }

    public boolean onContextItemSelected(MenuItem item) {

        AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();

        int position = info.position;

        switch (item.getItemId()) {
            case R.id.opcodemenu_shl:
                processor.putInstruction(position, Processor.INST_SHL);
                break;
            case R.id.opcodemenu_shr:
                processor.putInstruction(position, Processor.INST_SHR);
                break;
            case R.id.opcodemenu_in:
                processor.putInstruction(position, Processor.INST_IN);
                break;
            case R.id.opcodemenu_out:
                processor.putInstruction(position, Processor.INST_OUT);
                break;

            case R.id.opcodemenu_inc:
                processor.putInstruction(position, Processor.INST_INC);
                break;
            case R.id.opcodemenu_dec:
                processor.putInstruction(position, Processor.INST_DEC);
                break;
            case R.id.opcodemenu_zero:
                processor.putInstruction(position, Processor.INST_ZERO);
                break;
            case R.id.opcodemenu_swap:
                processor.putInstruction(position, Processor.INST_SWAP);
                break;

            case R.id.opcodemenu_jmpz:
                processor.putInstruction(position, Processor.INST_JMPZ);
                break;
            case R.id.opcodemenu_jmpa:
                processor.putInstruction(position, Processor.INST_JMPA);
                break;
            case R.id.opcodemenu_jmpt:
                processor.putInstruction(position, Processor.INST_JMPO);
                break;
            case R.id.opcodemenu_jmpf:
                processor.putInstruction(position, Processor.INST_JMPT);
                break;

            case R.id.opcodemenu_jpzz:
                processor.putInstruction(position, Processor.INST_JPZZ);
                break;
            case R.id.opcodemenu_jpza:
                processor.putInstruction(position, Processor.INST_JPZA);
                break;
            case R.id.opcodemenu_jpzt:
                processor.putInstruction(position, Processor.INST_JPZO);
                break;
            case R.id.opcodemenu_jpzf:
                processor.putInstruction(position, Processor.INST_JPZT);
                break;
        }

        mMemoryList.invalidateViews();

        return false;
    }

    private final Runnable stepper = new Runnable() {
        @Override
        public void run() {
            processor.step();
            refresh(false);
            if (!mStopStepping) {
                mHandler.postDelayed(this, 500);
            }
        }

    };

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getSupportMenuInflater().inflate(R.menu.options, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) {

        switch (item.getItemId()) {
            case R.id.options_load:
                load();
                return true;
            case R.id.options_save:
                save();
                return true;
            case R.id.options_clear:
                clear.show();
                return true;
            case R.id.options_help:
                help.show();
                return true;
        }

        return false;
    }

    private Intent createFileIntent(boolean create) {
        Intent intent = new Intent(ProblemMachine.this,
                com.ipaulpro.afilechooser.FileChooserActivity.class);
        intent.setAction(create ? Intent.ACTION_SEND : Intent.ACTION_GET_CONTENT);
        intent.putExtra(FileChooserActivity.EXTRA_OVERRIDEPATH,
                ((ProblemMachineApplication)getApplication()).getDataDir().getAbsolutePath());
        return intent;
    }

    private void save() {
        startActivityForResult(createFileIntent(true), REQUEST_CODE_SAVE);
    }

    private void load() {
        startActivityForResult(createFileIntent(false), REQUEST_CODE_LOAD);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            final Uri uri = data.getData();
            File file = FileUtils.getFile(uri);
            switch (requestCode) {
                case REQUEST_CODE_LOAD:
                    Log.d(TAG, "loading from " + file.getAbsolutePath());
                    // if (resultCode == RESULT_OK) {
                    // // The URI of the selected file
                    // final Uri uri = data.getData();
                    // // Create a File from this Uri
                    // File file = FileUtils.getFile(uri);
                    // }
                    if (processor.loadState(file)) {
                        refresh(true);
                        Toast.makeText(mContext, "Loaded!", Toast.LENGTH_SHORT).show();
                    } else
                        Toast.makeText(mContext, "Load failed", Toast.LENGTH_SHORT).show();

                    break;
                case REQUEST_CODE_SAVE:
                    Log.d(TAG, "saving to " + file.getAbsolutePath());
                    if (processor.saveState(file))
                        Toast.makeText(mContext, "Saved!", Toast.LENGTH_SHORT).show();
                    else
                        Toast.makeText(mContext, "Save failed", Toast.LENGTH_SHORT).show();
                    break;

            }
        }
    }

}
